package com.chenjiajin.controller;

import com.chenjiajin.domain.JsonResult;
import com.chenjiajin.domain.MultipartFileParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 大文件上传
 * 断点续传.秒传.分块上传
 */
@Slf4j
@RestController
@RequestMapping("/bigfile")
public class BigFileController {

    // https://glory.blog.csdn.net/article/details/114284285?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-114284285-blog-79102871.topnsimilarv1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2-114284285-blog-79102871.topnsimilarv1&utm_relevant_index=5

    // http://localhost/upload.html

    @Value("${breakpoint.upload.dir}")
    private String fileStorePath;

    /**
     * 秒传检查
     *
     * @param fileMd5 文件的MD5值
     * @Title: 判断文件是否上传过，是否存在分片，断点续传
     * 文件已存在，下标:-1
     * 文件没有上传过，下标:0
     * 文件上传中断过，返回当前已经上传到的下标
     */
    @PostMapping("/check")
    public JsonResult checkBigFile(String fileMd5) {

        JsonResult jr = new JsonResult();

        // 秒传检查
        File mergeMd5Dir = new File(fileStorePath + "/" + "merge" + "/" + fileMd5);

        // 判断文件夹存在
        if (mergeMd5Dir.exists()) {

            //mergeMd5Dir.mkdirs();       // 创建多层级文件夹
            jr.setResultCode(-1);       // 文件已存在，下标为-1

        } else {

            // 文件夹不存在
            File dir = new File(fileStorePath + "/" + fileMd5);
            File[] childs = dir.listFiles();   // 读取目录里的所有文件
            if (childs == null) {
                jr.setResultCode(0);       // 文件没有上传过，下标为零
            } else {
                jr.setResultCode(childs.length - 1);   // 文件上传中断过，返回当前已经上传到的下标
            }

        }
        return jr;


    }

    /**
     * 分片上传文件 - 断点续传
     *
     * @param param 上传文件的参数信息
     */
    @PostMapping("/upload")
    public void filewebUpload(MultipartFileParam param, HttpServletRequest request) {

        // 上传的文件名
        String fileName = param.getName();
        // 文件每次分片的下标
        int chunkIndex = param.getChunk();

        // 工具方法: 判断是不是多部分内容
        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (isMultipart) {
            File file = new File(fileStorePath + "/" + param.getMd5());
            if (!file.exists()) {  // 第n个分片的文件夹是否存在
                file.mkdir();      // 如果不存在 , 则创建这个分片的文件夹
            }
            File chunkFile = new File(fileStorePath + "/" + param.getMd5() + "/" + chunkIndex);
            try {
                FileUtils.copyInputStreamToFile(param.getFile().getInputStream(), chunkFile);  // 将上传的流的内容(文件分片) 转为本地的文件
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        log.info("文件-:{}的小标-:{},上传成功", fileName, chunkIndex);
    }

    /**
     * 分片上传成功之后，合并文件
     */
    @PostMapping("/merge")
    public JsonResult filewebMerge(HttpServletRequest request) throws IOException {

        // 获取请求参数
        String fileName = request.getParameter("fileName");  // 该请求是application/x-www-form-urlencoded(表单提交) , 不能在请求体中拿到数据
        String fileMd5 = request.getParameter("fileMd5");

        // 读取目录里的所有文件
        File dir = new File(fileStorePath + "/" + fileMd5);
        File[] childs = dir.listFiles();
        if (childs == null || childs.length == 0) {
            return null;
        }

        // 转成文件集合
        List<File> fileList = new ArrayList<>(Arrays.asList(childs));
        // 排序
        fileList.sort((o1, o2) -> {
            if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
                return -1;
            }
            return 1;
        });

        // 合并后的文件
        File outputFile = new File(fileStorePath + "/" + "merge" + "/" + fileMd5 + "/" + fileName);

        // 创建文件
        if (!outputFile.exists()) {  // 正常情况下 应该不存在该文件
            File mergeMd5Dir = new File(fileStorePath + "/" + "merge" + "/" + fileMd5);
            if (!mergeMd5Dir.exists()) {
                mergeMd5Dir.mkdirs();
            }
            log.info("创建文件");
            outputFile.createNewFile();
        }

        // 使用nio进行快速的文件拷贝
        FileChannel outChannel = new FileOutputStream(outputFile).getChannel(); // 输出流
        FileChannel inChannel = null;   // 输入流
        try {
            for (File file : fileList) {
                inChannel = new FileInputStream(file).getChannel();  // 遍历每个分片文件
                inChannel.transferTo(0, inChannel.size(), outChannel);
                inChannel.close();
                // 删除分片
                file.delete();
            }
        } catch (Exception e) {
            e.printStackTrace();
            outputFile.delete();    // 发生异常，文件合并失败 ，删除创建的文件
            dir.delete();           // 删除文件夹
        } finally {
            if (inChannel != null) {  // 遍历完需要关闭输入流
                inChannel.close();
            }
            outChannel.close();
        }

        dir.delete(); //删除分片所在的文件夹

        return null;
    }
}